#include "World.h"
#include "SharedConstants.h"
#include "Ground.h"
#include "WorldObject.h"
#include "ModelEvents.h"
#include "ObjParser.h"
#include "Tree.h"
#include "Human.h"
#include "WorldInfoController.h"
#include "HeightMap.h"
#include "FerrisWheel.h"
#include "Bench.h"
#include "Model3DStorage.h"
#include "BalloonsShop.h"
#include "HeightMapCoordinates.h"

namespace
  {
  void ShiftHeightMap(HeightMap& io_height_map, double i_height)
    {
    size_t size_x = io_height_map.GetSizeX();
    size_t size_y = io_height_map.GetSizeY();

    for (size_t i = 0; i < size_x; ++i)
      {
      for (size_t j = 0; j < size_y; ++j)
        {
        io_height_map.Set(i, j, io_height_map.Get(i, j) + i_height);
        }
      }
    }
  }

World::World()
  {
  HeightMap height_map = HeightMap(GroundConstants::HEIGHTMAP_SIZE, GroundConstants::HEIGHTMAP_SIZE);
  GeneratedHeightMapWithDiamondSquare(height_map, GroundConstants::PLAINS_PERCENTAGE, GroundConstants::MAX_HEIGHT);
  ShiftHeightMap(height_map, GroundConstants::MAX_HEIGHT / 4);
  mp_ground.reset(new Ground(height_map, GroundConstants::CELL_SIZE, GroundConstants::WATER_LEVEL));
	mp_world_info_controller.reset(new WorldInfoController(*mp_ground, m_renderable));
  }

World::World(const HeightMap& i_heightmap)
  {
  mp_ground.reset(new Ground(i_heightmap, GroundConstants::CELL_SIZE, GroundConstants::WATER_LEVEL));
  mp_world_info_controller.reset(new WorldInfoController(*mp_ground, m_renderable));
  }

void World::AddTree(const TVector3& i_position)
  {
	auto p_tree = std::make_shared<Tree>(*this, ETreeType::TT_DEFAULT_TREE);
	p_tree->SetPosition(i_position.X(), i_position.Y(), i_position.Z());
	m_world_objects.push_back(p_tree);
  }

void World::AddHuman(const TVector3& i_position)
  {
  auto p_human = std::make_shared<Human>(*this, EHumanTypes::HT_DEFAULT_HUMAN, i_position, *(GetWorldInfoController()));
	m_world_objects.push_back(p_human);
  }

void World::AddHuman(const TVector3& i_position, EBalloonColor i_color)
  {
	auto p_human = std::make_shared<Human>(*this, EHumanTypes::HT_DEFAULT_HUMAN, i_position, *(GetWorldInfoController()),
    boost::optional<EBalloonColor>(i_color));
	m_world_objects.push_back(p_human);
  }

void World::AddFerrisWheel(const TVector3& i_position)
  {
  auto p_ferris_wheel = std::make_shared<FerrisWheel>(*this, EFerrisWheelType::FWT_DEFAULT_FERRIS_WHEEL); 
  p_ferris_wheel->SetPosition(i_position.X(), i_position.Y(), i_position.Z());
  
  m_world_objects.push_back(p_ferris_wheel);
  }

void World::AddBench(TVector2 i_point)
  {
  auto p_bench = std::make_shared<Bench>(*this, *(GetWorldInfoController()));

  p_bench->SetPosition(i_point.X(),i_point.Y(),mp_ground->GetHeightForXY(i_point) * GroundConstants::CELL_SIZE);
  m_world_objects.push_back(p_bench);
  }

void World::AddBalloonsShop(const TVector3& i_position)
  {
	auto p_balloons_shop = std::make_shared<BalloonsShop>(*this);

  p_balloons_shop->SetPosition(i_position.X(), i_position.Y(), i_position.Z());
	m_world_objects.push_back(p_balloons_shop);
  }

void World::AddRenderable(IRenderable* i_renderable)
	{
	m_renderable[i_renderable->GetName()] = i_renderable;
	}

void World::AddAnimated(IAnimated *i_animated)
	{
	m_animated.push_back(i_animated);
	}

void World::AddMovable(IMovable *i_movable)
  {
  m_movable.push_back(i_movable);
  }

void World::RemoveRenderable(IRenderable *i_renderable)
  {
  m_renderable.erase(i_renderable->GetName());
  }

void World::RemoveAnimated(IAnimated *i_animated)
  {
  m_animated.erase(std::remove(m_animated.begin(), m_animated.end(), i_animated), m_animated.end());
  }

void World::RemoveMovable(IMovable *i_movable)
  {
  m_movable.erase(std::remove(m_movable.begin(), m_movable.end(), i_movable), m_movable.end());
  }

void World::DeleteObj(const WorldObject* i_object)
  {
  auto remove_it = std::remove_if(m_world_objects.begin(), 
    m_world_objects.end(), 
    [&](std::shared_ptr<WorldObject> &i_rend_obj) -> bool
    {
    if(i_object == i_rend_obj.get())
      return true;
    else
      return false;
    }
  );
  m_world_objects.erase(remove_it, m_world_objects.end());  
  mp_world_info_controller->UpdateInfo(m_renderable);
  }

World::~World()
  {
  m_world_objects.clear();

  m_animated.clear();
  m_renderable.clear();
  m_movable.clear();
  }

std::map<std::string, IRenderable*> World::Renderable() const
  {
	return m_renderable;
  }

IRenderable* World::GetRenderableByName(const std::string& i_name) const
  {
  return m_renderable.at(i_name);
  }

/**
* Returns a vector from all objects that are animated
* (e.g they implement IAnimated and provide NextFrame() method)
*/
std::vector<IAnimated*> World::Animated() const
  {
	return m_animated;
    }

std::vector<IMovable*> World::Movable() const
  {
  return m_movable;
  }

Ground* World::GetGround()
  {
  return mp_ground.get();
  }

const Ground* World::GetGround() const
  {
  return mp_ground.get();
  }


void World::SubscribeOnChangeEvent(Observer i_callback) const
  {
  m_changed_event.AddSubscriber(i_callback);
  }

void World::GenerateChangeEvent()
  {
	mp_world_info_controller->UpdateInfo(m_renderable);
  m_changed_event.Notify(EventArguments(""));
  }

void World::SubscribeOnLandscapeChangeEvent(Observer i_callback) const
  {
  m_landscape_changed_event.AddSubscriber(i_callback);
  }
   
void World::GenerateLandscapeChangeEvent()
  {
  mp_world_info_controller->UpdateInfo(m_renderable);
  m_landscape_changed_event.Notify(EventArguments(""));
  }

void World::SubscribeOnChangeObjectWithoutLandscapeEvent(Observer i_callback) const
  {
  m_objects_without_landscape_changed_event.AddSubscriber(i_callback);
  }

void World::GenerateChangeObjectWithoutLandscapeEvent()
  {
  mp_world_info_controller->UpdateInfo(m_renderable);
  m_objects_without_landscape_changed_event.Notify(EventArguments(""));
  }

void World::SubscribeOnMoveEvent(Observer i_callback) const
  {
  m_object_moved_event.AddSubscriber(i_callback);
  }

void World::GenerateMovedEvent(const std::string& i_object_name) const
  {
  m_object_moved_event.Notify(EventArguments(i_object_name));
  }

void World::SubscribeOnAnimateEvent(Observer i_callback) const
  {
  m_animate_event.AddSubscriber(i_callback);
  }

void World::GenerateAnimateEvent(const std::string& i_object_name) const
  {
  m_animate_event.Notify(EventArguments(i_object_name));
  }

TVector3 World::GetObjectPosition(const std::string& i_object_name) const
  {
  if (m_renderable.find(i_object_name) != m_renderable.end())
	  return m_renderable.at(i_object_name)->GetPosition();

  throw std::logic_error("GetObjectPosition: error object name");
  }

double World::GetObjectAngleAroundY(const std::string& i_object_name) const
  {
  if (m_renderable.find(i_object_name) != m_renderable.end())
	  return m_renderable.at(i_object_name)->GetAngleAroundY();
  
  throw std::logic_error("GetObjectAngleAroundY: error object name");
  }


const std::shared_ptr<WorldInfoController> World::GetWorldInfoController() const
  {
  return mp_world_info_controller;
  }

void World::ClearModels()
	{
	m_world_objects.clear();

  m_animated.clear();
  m_renderable.clear();
  m_movable.clear();
	}

void World::ResetGround(std::unique_ptr<Ground>& i_ground)
	{
	mp_ground = std::move(i_ground);
	mp_world_info_controller.reset(new WorldInfoController(*mp_ground, m_renderable));
	}

template<typename T> void World::RefreshObjectsOnGroundPositions()  
  {
  std::vector<T*> objects = GetItems<T>();

  for(auto iter : objects)
  {
    TVector3 pos = iter->GetPosition();
    double height = mp_ground->GetHeightForXY(TVector2(pos.X(), pos.Y()));
    iter->SetPosition(pos.X(), pos.Y(), height * GroundConstants::CELL_SIZE);   
    }
  }
